<?php
// @formatter:off
/**
 * @file WCTag.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-08-20 00:23:06 +0000 (Tue, 20 Aug 2013) $
 *
 * $Id: WCTag.php 102 2013-08-20 00:23:06Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0/includes/representations/WCTag.php $
 */
// @formatter:on

/** @fixme comics of a tag should use WCGCotainer. */

/**
 * @class WCTag This class represents entries saved in the table "%tags".
 */
class WCTag extends WCGTableRepresentation implements WCPreviews {
	/**
	 * @var string Special character to use as separator in column
	 * tag_previews.
	 */
	public static $PreviewsSeparator = "|";
	/**
	 * @var int This constant holds the maximum length of column
	 * tag_previews.
	 */
	public static $PreviewsMaxLength = 640;
	/**
	 * @var string[] This variable holds the list of columns that cannot be
	 * directly modified by the pseudo-method __set().
	 * In this case tag_id and tag_group.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"group"
	);
	/**
	 * @var string[] This variable holds the list of columns that must be
	 * treated as booleans and then send to the database as 'Y' and 'N' by
	 * the pseudo-method __set().
	 * In this case tag_valid_preview and tag_indexed.
	 */
	protected $_PropertiesBoolean = array(
		"valid_preview",
		"indexed"
	);
	/**
	 * @var string This is the prefix of every column on the table
	 * '%tags'.
	 */
	protected $_PropertiesPrefix = "tag_";
	/**
	 * @var string This is the name of the represented table. In this case,
	 * '%tags'.
	 */
	protected $_PropertiesTable = "tags";
	/**
	 * @var WCTagsHolder Holder where this tag will be hanging.
	 */
	protected $_holder = null;
	/**
	 * @var boolean This flag indicates if this is a special tag.
	 */
	protected $_special = false;
	/**
	 * Class constructor.
	 */
	public function __construct() {
		//
		// Calling parents constructor.
		parent::__construct();
		//
		// Loading global pointers.
		global $wcTagsHolder;
		//
		// Creating a shortcut to its holder.
		$this->_holder = $wcTagsHolder;
	}
	//
	// Public methods.
	/**
	 * This method allows to set current tag to a comic.
	 *
	 * @param WCComic $comic Comic to be added as a member of this tag.
	 * @return boolean Returns true when the comic was successfully added.
	 */
	public function add(WCComic &$comic) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks();
			//
			// If there are no problems and the comic doesn't have the
			// current tag, it is added.
			if($out && !$this->has($comic)) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to insert the new member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}tag_members (\n";
					$query .= "                mem_tag, mem_comic, mem_group, mem_added)\n";
					$query .= "        values (:id, :comic, :group, sysdate())";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":id" => $this->id,
					":group" => $this->group,
					":comic" => $comic->id
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params)) {
					//
					// Loading global pointers.
					global $wcPageCache;
					//
					// Invalidating possible page caches.
					// @{
					$wcPageCache->invalidateByTag($this);
					$wcPageCache->invalidateByComic($comic);
					// @}
					//
					// Tag previews are no longer valid.
					$this->valid_preview = false;
					//
					// Tag has a new member.
					$this->count++;
					//
					// At this point, comic was successfully
					// added as member.
					$out = true;
				}
			}
		}
		//
		// Returning addition status.
		return $out;
	}
	/**
	 * This method allows to add all members of a directory to this tag.
	 *
	 * @param WCDirectory $directory Directory holding the items to be added.
	 * @return boolean Returns true when all members of the directory were added.
	 */
	public function addDirectory(WCDirectory &$directory) {
		//
		// Making special checks.
		$out = $this->specialChecks();
		//
		// Checking if it's ok to add elements.
		if($out) {
			//
			// Loading global pointers.
			global $wcPageCache;
			//
			// Invalidating possible page caches.
			$wcPageCache->invalidateByDirectory($directory);
			//
			// Adding all directoriy's members.
			foreach($directory->membersFull() as $comic) {
				$this->add($comic);
			}
		}
		//
		// Returns the addition result.
		return $out;
	}
	/**
	 * This method allows to add all members of a tag to this one.
	 *
	 * @param WCTag $tag Tag holding the items to be added.
	 * @return boolean Returns true when all members were added.
	 */
	public function addTag(WCTag &$tag) {
		//
		// Making special checks.
		$out = $this->specialChecks();
		//
		// Checking if it's ok to add elements.
		if($out) {
			//
			// Loading global pointers.
			global $wcPageCache;
			//
			// Invalidating possible page caches.
			$wcPageCache->invalidateByTag($tag);
			//
			// Adding all tag's members.
			foreach($tag->membersFull() as $comic) {
				$this->add($comic);
			}
		}
		//
		// Returns the addition result.
		return $out;
	}
	/**
	 * This method allows to know how many comics belong to this tag.
	 *
	 * @return int Returns the number of members.
	 */
	public function count() {
		return $this->count;
	}
	/**
	 * This method creates a new random previews list based on tags's comics,
	 * and sets it in column tag_previews.
	 *
	 * @return boolean Returns true if a previews list was generated.
	 */
	public function generatePreviews() {
		//
		// Setting a default value to be returned.
		$out = false;
		//
		// Loading global pointers.
		global $wcPageCache;
		global $wcDefaults;
		//
		// Invalidating cached items related with this tag.
		$wcPageCache->invalidateByTag($this);
		//
		// Creating a temporary list for previews.
		$previews = array();
		//
		// Loading current tag's comics.
		$comics = $this->membersFull();
		//
		// Counting how many comics are involved.
		$comicCount = count($comics);
		//
		// This is a good opportunity to update some statistics.
		$this->count = $comicCount;
		//
		// Checking if there are comics to use.
		if($comicCount > 0) {
			//
			// Each tag can't have more than ten previews.
			$comicCount = $comicCount > 10 ? 10 : $comicCount;
			//
			// Checking if there are more than one preview.
			if($comicCount > 1) {
				//
				// Randomly selecting some previews.
				foreach(array_rand($comics,$comicCount) as $id) {
					$previews[] = $comics[$id]->previewUri();
				}
			} else {
				//
				// Adding the only preview.
				$previews[] = $comics[0]->previewUri();
			}
			//
			// Removing default preview images.
			foreach($previews as $key => $value) {
				if($value == $wcDefaults["preview"]) {
					unset($previews[$key]);
				}
			}
			//
			// Counting how many previews where chosen.
			$count = count($previews);
			//
			// Checking that there's something to assign.
			if($count && $count == $comicCount) {
				//
				// Pasting and assigning previews.
				$this->previews = implode(self::$PreviewsSeparator, $previews);
				//
				// At this point, previews where generated and
				// assigned, and they are valid.
				// @{
				$this->valid_preview = true;
				$out = true;
				// @}
			}
		}
		//
		// Returning generation result.
		return $out;
	}
	/**
	 * This method allows to know if certain comic belongs to this tag.
	 *
	 * @param WCComic $comic Comics to check.
	 * @return boolean Returns true when the requested comic has this tag.
	 */
	public function has(WCComic &$comic) {
		//
		// Setting default value to be returned.
		$found = false;
		//
		// Building database parameters.
		$params = array(
			":group" => $this->group,
			":tag" => $this->id,
			":comic" => $comic->id
		);
		//
		// Creating a statement to search a specific member.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "select  1\n";
			$query .= "from    {$this->_dbprefix}tag_members\n";
			$query .= "where   mem_tag   = :tag\n";
			$query .= " and    mem_group = :group\n";
			$query .= " and    mem_comic = :comic";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Checking member association.
		if($stmt->execute($params) && $stmt->rowCount() > 0) {
			$found = true;
		}
		//
		// Returning search result.
		return $found;
	}
	/**
	 * This method allows to know if this tag has a preview image.
	 *
	 * @return boolean Returns true when this tag has at least one preview.
	 */
	public function hasPreview() {
		return $this->hasPreviews();
	}
	/**
	 * This method allows to know if this tag has a list of preview images.
	 *
	 * @return boolean Returns true when this tag has at least one preview.
	 */
	public function hasPreviews() {
		return ($this->previews != "");
	}
	/**
	 * This method allows to know if this is a special tag.
	 *
	 * @return boolean Returns true when this is a special tag.
	 */
	public function isSpecial() {
		return $this->_special;
	}
	public function load($id, WCGroup &$group) {
		//
		// Setting a default status before loading.
		$this->_ok = false;
		//
		// Setting a default special-tag status before loading.
		$this->_special = false;
		//
		// Calling parent's method.
		parent::load($id, $group);
		//
		// Checking if parent class loaded without problems.
		if($this->ok()) {
			//
			// Loading global pointers.
			global $wcSpecialTags;
			//
			// Cleaning the list of previews
			$this->_info["tag_previews"] = $this->_info["tag_previews"] ? $this->_info["tag_previews"] : false;
			//
			// Checking if it's a special tag.
			$this->_special = in_array($this->name, $wcSpecialTags);
		}
		//
		// Returns the final status of this object.
		return $this->ok();
	}
	/**
	 * This method allows to get a list of ids of all comics in this tag.
	 *
	 * @return int[] Returns all comic members ids.
	 */
	public function members() {
		//
		// Setting default value to be returned.
		$out = array();
		//
		// Checking that there are no issues with this tag.
		if($this->ok()) {
			//
			// Creating a statement request members of this tag.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query .= "from    {$this->_dbprefix}tag_members\n";
				$query .= "                inner join {$this->_dbprefix}comics\n";
				$query .= "                        on(mem_comic = cmc_id and mem_group = cmc_group)\n";
				$query .= "where   mem_tag   = :tag\n";
				$query .= " and    mem_group = :group\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":group" => $this->group,
				":tag" => $this->id
			);
			//
			// Requesting members.
			if($stmt->execute($params)) {
				//
				// Building a list of comic ids.
				foreach($stmt->fetchAll() as $row) {
					$out[] = $row["mem_comic"];
				}
			}
		}
		//
		// Returning a list of comic ids.
		return $out;
	}
	/**
	 * This method allows to get a list of all comics in this tag.
	 *
	 * @return WCComic[] Returns all comic members as WCComic objects.
	 */
	public function membersFull() {
		//
		// Setting default value to be returned.
		$out = array();
		//
		// Walking over each member id.
		foreach($this->members() as $comic) {
			//
			// Loading global pointers.
			global $wcComicsHolder;
			//
			// Getting the comic from its holder.
			$aux = $wcComicsHolder->item($comic, $this->_group);
			//
			// If there are no issues, comic is added to the list.
			if($aux && $aux->ok()) {
				$out[] = $aux;
			}
		}
		//
		// Returning a list of comics.
		return $out;
	}
	/**
	 * This method allows to get this tag's name.
	 *
	 * @return string Returns a name.
	 */
	public function name() {
		return $this->name;
	}
	/**
	 * This method allows to get the thumbnail associated with current tag.
	 *
	 * @return string Returns an image URIs.
	 */
	public function preview() {
		//
		// Checking if there are previews set.
		if($this->previews) {
			//
			// Getting the list of possible thumbnails.
			$list = $this->previews();
			//
			// Returning the first URI from the list.
			return array_shift($list);
		} else {
			//
			// Where there is no preview, it returns an empty string.
			return "";
		}
	}
	/**
	 * This method allows to get the list of thumbnails associated with
	 * current tag.
	 *
	 * @return string[] Returns a list of image URIs.
	 */
	public function previews() {
		//
		// Checking if there are previews set.
		if($this->previews) {
			//
			// Splitting the contents of column rul_previews and
			// returning them as an array.
			return explode(self::$PreviewsSeparator, $this->previews);
		} else {
			//
			// Where there are no previews, it returns an empty array.
			return array();
		}
	}
	/**
	 * This method allows to remove current tag.
	 *
	 * @return boolean Returns true on success.
	 */
	public function remove() {
		//
		// Removing every comic association with this tag.
		foreach($this->membersFull() as $comic) {
			$this->removeComic($comic);
		}
		//
		// Calling parent remover.
		return parent::remove();
	}
	/**
	 * This method allows to remove current tag from a comic.
	 *
	 * @param WCComic $comic Comic to be removed as a member of this tag.
	 * @return boolean Returns true when the comic was successfully removed.
	 */
	public function removeComic(WCComic &$comic) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks();
			//
			// If everything is ok, the comic is removed as member of
			// the tag.
			if($out) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to remove the member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from	{$this->_dbprefix}tag_members\n";
					$query .= "where		mem_tag   = :tag\n";
					$query .= " and		mem_group = :group\n";
					$query .= " and		mem_comic = :comic";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":tag" => $this->id,
					":group" => $this->group,
					":comic" => $comic->id
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params) && $stmt->rowCount() > 0) {
					//
					// Loading global pointers.
					global $wcPageCache;
					//
					// Invalidating possible page caches.
					$wcPageCache->invalidateByTag($this);
					$wcPageCache->invalidateByComic($comic);
					//
					// Tag previews are no longer valid.
					$this->valid_preview = false;
					//
					// Tag lost a member.
					$this->count--;
					//
					// At this point, comic was successfully
					// removed as member.
					$out = true;
				}
			}
		}
		//
		// Returning removal status.
		return $out;
	}
	/**
	 * This method allows to remove every comic inside a directory from this
	 * tag.
	 *
	 * @param WCDirectory $directory Directory to walk over.
	 * @return boolean Returns true when directory members were removed.
	 */
	public function removeDirectory(WCDirectory &$directory) {
		//
		// Performing special tag checks.
		$out = $this->specialChecks();
		//
		// Checking that it's possible to remove items.
		if($out) {
			//
			// Loading global pointers.
			global $wcPageCache;
			//
			// Invalidation cached items associated with directory.
			$wcPageCache->invalidateByDirectory($directory);
			//
			// Removing each directory member from this tag.
			foreach($directory->membersFull() as $comic) {
				$this->removeComic($comic);
			}
		}
		//
		// Returning removal result.
		return $out;
	}
	/**
	 * This method allows to remove every comic inside a tag from this
	 * tag.
	 *
	 * @param WCTag $tag Tag to walk over.
	 * @return boolean Returns true when tag members were removed.
	 */
	public function removeTag(WCTag &$tag) {
		//
		// Performing special tag checks.
		$out = $this->specialChecks();
		//
		// Checking that it's possible to remove items.
		if($out) {
			//
			// Loading global pointers.
			global $wcPageCache;
			//
			// Invalidation cached items associated with tag.
			$wcPageCache->invalidateByTag($tag);
			//
			// Removing each directory member from this tag.
			foreach($tag->membersFull() as $comic) {
				$this->removeComic($comic);
			}
		}
		//
		// Returning removal result.
		return $out;
	}
	/**
	 * This method allows to remove all members of the current tag.
	 *
	 * @return boolean Returns true when the tag was successfully emptied.
	 */
	public function removeAll() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks();
			//
			// If everything is ok, the comic is removed as member of
			// the tag.
			if($out) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Loading global pointers.
				global $wcPageCache;
				//
				// Invalidating possible page caches.
				$wcPageCache->invalidateByTag($this);
				//
				// Creating a statement to remove all members.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from	{$this->_dbprefix}tag_members\n";
					$query .= "where		mem_group = :group\n";
					$query .= " and		mem_tag   = :tag";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":tag" => $this->id,
					":group" => $this->group
				);
				//
				// Attempting to remove all member.
				if($stmt->execute($params)) {
					//
					// Tag lost all members.
					$this->count = 0;
					//
					// At this point, comic was successfully
					// emptied.
					$out = true;
				}
			}
		}
		//
		// Returning removal status.
		return $out;
	}
	//
	// Protected methods.
	/**
	 * This method performs checks to know if current tag operations are
	 * allowed.
	 *
	 * @return boolean Returns true when operations are allows.
	 */
	protected function specialChecks() {
		//
		// Setting default value to be returned.
		$out = true;
		//
		// Checking if current tag is a special one.
		if($this->_special) {
			//
			// Loading global pointers.
			global $wcProfile;
			global $wcErrno;
			global $wcError;
			//
			// Performing checks based on its name.
			switch($this->name) {
				case WC_SPECIAL_TAG_TRASH:
					//
					// Trash tag can only be added by a
					// ORGANIZER or more.
					if(!$wcProfile->granted(WC_PERM_COMIC_TRASH)) {
						$wcErrno = 403;
						$wcError = "Tag '".WC_SPECIAL_TAG_TRASH."' access denied";
						$out = false;
					}
					break;
			}
		}
		//
		// Returning checks result.
		return $out;
	}
	/**
	 * This is an internal method called by the pseudo-method __set()
	 *
	 * @param string $name Name of the parameter being modified.
	 * @param string $value Value to set to the property with name $name.
	 * @param string $realValue Value built by the pseudo-method __set().
	 * @return boolean Returns true when it is alright to modify the property.
	 */
	protected function subSet($name, &$value, &$realValue) {
		//
		// Loading global pointers.
		global $wcPageCache;
		//
		// When something in this tag changes, its cache is no linger
		// valid.
		if($wcPageCache) {
			$wcPageCache->invalidateByTag($this);
		}
		//
		// When its name name changes, it must be re-indexed.
		if($name == "name") {
			$this->indexed = false;
		}
		//
		// All modifications are allowed.
		return true;
	}
	//
	// Public Class Methods
	/**
	 * This is a sorting method based on action names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when the id of $a is greater than
	 * $b's.
	 */
	public static function IdSort(c &$a, WCTag &$b) {
		return $a->id > $b->id;
	}
	/**
	 * This is a sorting method based on action names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when the name of $a is greater than
	 * $b's.
	 */
	public static function NameSort(WCTag &$a, WCTag &$b) {
		return $a->name > $b->name;
	}
	/**
	 * This is the general sorting method and it can be used on usort and
	 * uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when $a goes after $b.
	 */
	public static function Sort(WCTag &$a, WCTag &$b) {
		return self::NameSort($a, $b);
	}
}
?>